(function() {

    var
    $u = juliet.using({
        func : 'juliet.core.func-0.1',
        AutocompleteCache : 'juliet.ui.input.withPopupList.autocomplete.AutocompleteCache-0.1',
        CustomInputWithPopupList : 'juliet.ui.input.withPopupList.CustomInputWithPopupList-0.1'
    }),
    $ = juliet.jQuery;

    var
    FAKE_CONTROL_ELEMENT_TEMPLATE = '<div class="custom-input disabled hidden"></div>',
    DEFAULT_FORMAT_URL = function(url, value) { return url + value + '/'; },
    KEY_UP_TIMEOUT = 300,
    MIN_CHARACTERS = 1;

    /**
     * Supported keyboard keys.
     */
    var KeyType = {

		RETURN : 13

	};

    var CustomInputWithPopupList = $u('CustomInputWithPopupList');

    var AutocompleteClass = function() {

        /**
         * @see AutocompleteCacheClass
         * @type Object
         * @private
         */
        this._cache = null;

        /**
         * Previous control element value.
         *
         * @type String
         * @private
         */
        this._previousValue = null;

        /**
         * Prevent propagation of "focus" and "blur" events when activated.
         *
         * @type Boolean
         * @private
         */
        this._suppressFocusEvents = false;

        /**
         * Control element's on key up timer.
         *
         * @type Number
         * @private
         */
        this._keyUpTimeout = null;

        /**
         * This element is visible when autocomplete in "disabled" state.
         *
         * @type JQueryElement
         * @private
         */
        this._fakeControlElement = null;

        // -------------------------------------------------------
        // -                        LOGIC                        -
        // -------------------------------------------------------

        /**
         * @param {String} value Control element value.
         */
        this._loadDataFromServer = function(value) {
            var
            formatUrl = this._options.formatUrl || DEFAULT_FORMAT_URL,
            url = formatUrl(this._options.url, encodeURIComponent(value));

            /*$.ajax({
                url : url,
                dataType: 'json',

                success : $u('func').proxy(function(response) {
                    this._setData(value, response);
                }, this)
            });*/
            this._setData(value, [ { title : 'Череповец', value : 0 }, { title : 'Вологда', value : 1 } ]);
        };

        /**
         * @param {String} value Control element value (cache key).
         */
        this._tryToLoadDataFromCache = function(value) {
            var cachedData = this._cache.get(value);

            if (cachedData) {
                this._setData(null, cachedData);
                return true;
            }

            return false;
        };

        /**
         * @param {String} value Control element value (cache key).
         * @param {Array} data Popup list data (cache value).
         * @private
         */
        this._setData = function(value, data) {
            if (data.length == 0) {
                this._hidePopupList();
            } else {
                if (value) {
                    this._cache.put(value, data);
                }

                this._popupList.setData({ data : data });
                this._popupList.show();

                this._suppressFocusEvents = true;
            }

            this._observer.notify('data-updated', data);
        };

        /**
         * Hide popup list.
         *
         * @private
         */
        this._hidePopupList = function() {
            this._popupList.hide();
            this._suppressFocusEvents = false;
        };

        /**
         * @private
         */
        this._clearKeyUpTimeout = function() {
            if (this._keyUpTimeout) {
                clearTimeout(this._keyUpTimeout);
                this._keyUpTimeout = null;
            }
        };

        /**
         * @private
         */
        this._resetSelectedItemIfControlElementValueChanged = function(e) {
            var value = this._controlElement.val();

            if (this.previousValue == value) return false;

            this.previousValue = value;

            if (e.keyCode == KeyType.RETURN) return false;

            // Set selected item directly to avoid setter's side effect.
            this._selectedItem = null;

            return true;
        };

        /**
         * @private
         */
        this._initializePopupListFormatTitleFunction = function() {
            this._popupList.getOptions().formatTitle = $u('func').proxy(function(title) {
                var regex = new RegExp('^(' + this._controlElement.val() + ')', 'gi');
                return title.replace(regex, '<strong>$1</strong>');
            }, this);
        };

        /**
         * @private
         */
        this._initializeCache = function() {
            this._cache = juliet.createObject($u('AutocompleteCache'));
        };

        //
        // UI EVENTS HANDLERS
        //

        /**
         * @private
         */
        this._popupListOnItemSelectedHandler = function(item) {
            this.setSelectedItem(item);
        };

        /**
         * @private
         */
        this._popupListOnHideFromHiderHandler = function() {
            this._suppressFocusEvents = false;
            this._controlElement.trigger('blur');
        };

        /**
         * @private
         */
        this._autocompleteOnEnabledHandler = function() {
            this._fakeControlElement.hide();
            this._controlElement.show();
        };

        /**
         * @private
         */
        this._autocompleteOnDisabledHandler = function() {
            this._fakeControlElement.show();
            this._controlElement.hide();
        };

        /**
         * @param {Object} item Selected item. Object with "title" and "value" fields.
         * @private
         */
        this._autocompleteOnItemSelectedHandler = function(item) {
            if (item) {
                this._previousValue = item.title;
                this._controlElement.val(item.title);
                this._controlElement.focus();
                this._hidePopupList();
            }
        };

        /**
         * @private
         */
        this._subscribeOnEvents = function() {
            var popupListObserver = this._popupList.getObserver();

            popupListObserver.subscribe('item-selected', this._popupListOnItemSelectedHandler, this);
            popupListObserver.subscribe('hide-from-hider', this._popupListOnHideFromHiderHandler, this);

            var autocompleteObserver = this._observer;

            autocompleteObserver.subscribe('enabled', this._autocompleteOnEnabledHandler, this);
            autocompleteObserver.subscribe('disabled', this._autocompleteOnDisabledHandler, this);
            autocompleteObserver.subscribe('item-selected', this._autocompleteOnItemSelectedHandler, this);
        };

        /**
         * @private
         */
        this._controlElementOnKeyUpHandler = function() {
            var value = this._controlElement.val();

            if (value.length < MIN_CHARACTERS) {
                this._setData(null, []);
                return;
            }

            if (!this._tryToLoadDataFromCache(value)) {
                this._loadDataFromServer(value);
            }
        };

        /**
         * @private
         */
        this._controlElementOnKeyUpHandlerWithDelay = function(e) {
            if (!this._resetSelectedItemIfControlElementValueChanged(e)) return;

            this._clearKeyUpTimeout();

            this._keyUpTimeout = setTimeout($u('func').proxy(function() {
                this._controlElementOnKeyUpHandler();
                this._keyUpTimeout = null;
            }, this), KEY_UP_TIMEOUT);
        };

        /**
         * @private
         */
        this._controlElementOnFocusHandler = function(e) {
            if (this._suppressFocusEvents) e.stopImmediatePropagation();
        };

        /**
         * @private
         */
        this._controlElementOnBlurHandler = function(e) {
            this._clearKeyUpTimeout();
            if (this._suppressFocusEvents) e.stopImmediatePropagation();
        };

        /**
         * @private
         */
        this._bindEventsHandlers = function() {
            $u('func').bind(this._controlElement, 'keyup', this._controlElementOnKeyUpHandlerWithDelay, this);
            $u('func').bind(this._controlElement, 'focus', this._controlElementOnFocusHandler, this);
            $u('func').bind(this._controlElement, 'blur', this._controlElementOnBlurHandler, this);
        };

        //
        // UI INITIALIZATION
        //

        /**
         * @private
         */
        this._updateControlElement = function() {
            this._controlElement.addClass(this._popupList.getUid());
            this._controlElement.attr('autocomplete', 'off');
        };

        /**
         * @private
         */
        this._initializeFakeControlElement = function() {
            this._fakeControlElement = $(FAKE_CONTROL_ELEMENT_TEMPLATE);
            this._controlElement.before(this._fakeControlElement);
        };

        //
        // API
        //

        this.initialize = function(o) {
            // Call parent's initialize function.
            AutocompleteClass.prototype.initialize.call(this, o);

            this._updateControlElement();
            this._initializeFakeControlElement();

            this._initializePopupListFormatTitleFunction();
            this._initializeCache();
            this._bindEventsHandlers();
            this._subscribeOnEvents();
        };

    };

    AutocompleteClass.prototype = new CustomInputWithPopupList();

    juliet.registerModule('juliet.ui.input.withPopupList.autocomplete', 'Autocomplete-0.1', AutocompleteClass);

})();
